How to set up autotools for 42sh ?
On this page you can find out how to get started with Autotools
for 42sh.
Architecture of your 42sh
We will start by looking at how to create a proper architecture for your shell.
As an example, we will use a sample project with an architecture containing sub-folders. For each sub-folder, we will want to create the corresponding static library to then be able to produce the final binary.
Sample architecture:
42sh
├── src
│ ├── ast
│ │ ├── ast.h
│ │ ├── ast.c
│ │ └── Makefile.am
│ ├── parser
│ │ ├── parser.h
│ │ ├── parser.c
│ │ └── Makefile.am
│ ├── Makefile.am
│ └── 42sh.c
├── configure.ac
└── Makefile.am
What is Autoconf?
Autoconf is a tool for configuring the build, for example checking that our compiler has the right flags for our project, checking that the libraries are installed correctly and generating the makefiles.
Creating configure.ac
Start by creating a file called configure.ac
at your project’s root
directory. This file is used by autoconf to create the configure
POSIX shell script that users must run before building.
The file should include at a minimum the M4 macros AC_INIT
and AC_OUTPUT
. You are not required to have any knowledge of the
M4 language to utilize these macros and the relevant ones are
documented.
To put it simply, an M4 macro is just a templated shell script.
The AC_INIT
macro can include the package name, version, and
email address for reporting bugs, the project URL, and, optionally,
the name of the source TAR file.
AC_INIT
has several parameters: the first allows you to say that you
will have makefiles in sub-folders, the second allows you to deactivate
the fact that the project must respect the GNU
architecture which has
the constraint of having different files at the root such as NEWS
,
Changelog
, AUTHORS
, README
and others.
The AC_OUTPUT
macro is much simpler and accepts no arguments.
The macros for generating a Makefile are as follows: AM_INIT_AUTOMAKE
,
which does not require any arguments, and AC_CONFIG_FILES
, which takes
the name you want to assign to your output file.
You need to incorporate a macro that corresponds to the specific
compiler your project requires. For instance, a project coded in C
would necessitate the use of AC_PROG_CC
, as outlined in the Autoconf
documentation.
AX_COMPILER_FLAGS
is used to check if the compiler supports a
given set of flags
Macros prefixed with AX_
come from the package autoconf-archive
.
It contains a set of macros to perform additional checks.
Checkout the list of macros defined in autoconf-archive.
The AM_PROG_AR
macro verifies that ar
, a tool to creates archives,
is present on your system and configures build options associated
with archiving if it is available. The AC_PROG_RANLIB
macro checks
for the existence of the ranlib
tool on the system.
# Init the 42sh project
AC_INIT([42sh], [1.0], [42sh@assistants.epita.fr])
# Setup automake
AM_INIT_AUTOMAKE([subdir-objects] [foreign])
# Pretty display of makefile rules
AM_SILENT_RULES([yes])
# Enable ar for Makefile
AM_PROG_AR
# Check if a ranlib is available
AC_PROG_RANLIB
# Check if a compiler is available for c
AC_PROG_CC
# Check if a compiler have this list of flags
AX_COMPILER_FLAGS([], [], [], [-Wall -Wextra -Werror -Wvla -pedantic -std=c99])
# List Makefile in subdirectories
AC_CONFIG_FILES([
Makefile
src/Makefile
src/ast/Makefile
src/parser/Makefile
])
AC_OUTPUT
Setting up the build environment
To create the configure script from the configure.ac
file, you
can use autoreconf
with the --install
flag. This command
processes the Autoconf macros and generates the configure
script. Here is how you do it:
42sh$ autoreconf --install
Now that you have your configure
script, it is time to configure
the build environment. This step involves detecting your system's
capabilities, setting up paths, and ensuring all the necessary tools
and libraries are available. To do so, simply run:
42sh$ ./configure
What is Automake?
The Makefile.am
script is like a blueprint for generating
Makefile.in
. It is similar to how configure.ac
simplifies creating
complex files. Automake is the tool you will use to build the Makefile
and can be customized further using autoconf.
Let's move on to the creation of the various static libraries.
Creating libast.a library
In this section, the goal is to create the Makefile.am
to produce
src/ast/libast.a
.
Automake is based on a template system to generate makefiles. To do this, we need to define different variables to define our target.
lib_LIBRARIES
is an automake variable where we define the name of
the library we want to produce. The lib_
prefix is a convention
to indicate that this is a library target. We are going to define a
variable for all of the sources of our library: libast_a_SOURCES
.
# Name of the libaries
lib_LIBRARIES = libast.a
# List of file to compile in libast.a
libast_a_SOURCES = \
ast.c \
ast.h
Automake has a variable system based on convention which is the
name_of_target_AM_FEATURE
.
For example:
name_of_target_CFLAGS
: Flags compiler for the target.name_of_target_LDFLAGS
: Linker flags for the target.
Checkout the GNU documentation
In name of target, .
are replaced by _
for example libast.a
becomes libast_a_AM_FEATURE
.
The next part of the Makefile.am
specifies compiler and preprocessor
flags for building the library.
libast_a_CPPFLAGS = -I$(top_srcdir)/src
libast_a_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic
libast_a_CPPFLAGS
sets the preprocessor flags. It specifies
-I$(top_srcdir)/src
, which is used to include
$(top_srcdir)/src
directory with -I
option.
This flag ensures that the compiler can find header
files in the source and build directories.
libast_a_CFLAGS
sets the compiler flags.
You need to add the .h
in the sources, they are important for
creating the package.
The noinst_LIBRARIES
variable is used to specify libraries (object
code files or static libraries) that should not be installed when
the make install
command is run.
lib_LIBRARIES = libast.a
libast_a_SOURCES = \
ast.c \
ast.h
libast_a_CPPFLAGS = -I$(top_srcdir)/src
libast_a_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic
noinst_LIBRARIES = libast.a
Source code:
#ifndef AST_H
#define AST_H
void print_ast(void);
#endif /* ! AST_H */
#include "ast.h"
#include <stdio.h>
#include "parser/parser.h"
void print_ast(void)
{
puts("ast !!!");
print_parser();
}
Creating libparser.a library
We are now going to repeat the same process for parser.
lib_LIBRARIES = libparser.a
libparser_a_SOURCES = \
parser.c \
parser.h
libparser_a_CPPFLAGS = -I$(top_srcdir)/src
libparser_a_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic
noinst_LIBRARIES = libparser.a
Code source:
#ifndef PARSER_H
#define PARSER_H
void print_parser(void);
#endif /* ! PARSER_H */
#include "parser.h"
#include <stdio.h>
void print_parser(void)
{
puts("parser !!!");
}
Creating 42sh binary
Now that we have created the libraries, which are the dependencies of
the 42sh binary, we can proceed on producing the binary by configuring
the src/Makefile.am
file.
SUBDIRS = ast \
parser
Here, the SUBDIRS
variable is used to specify two subdirectories that
contains Makfile.am
files: ast and parser. Each of these subdirectories
likely contains code related to abstract syntax trees and parsing,
respectively.
bin_PROGRAMS = 42sh
The bin_PROGRAMS
variable specifies the 42sh binary.
42sh_SOURCES = 42sh.c
42sh_CPPFLAGS = -I%D%
42sh_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic
The 42sh_SOURCES
variable specifies the source file for the program
. The 42sh_CPPFLAGS
and 42sh_CFLAGS
variables respectively define
preprocessor and compiler flags.
42sh_LDADD = \
ast/libast.a \
parser/libparser.a
The 42sh_LDADD
variable lists the dependencies the main program
relies on during the linking phase. In our case, it includes static
libraries (libast.a
and libparser.a
).
The order of dependencies in the link is important
#include "ast/ast.h"
int main(void)
{
print_ast();
return 0;
}
# define the sub directories
SUBDIRS = ast \
parser
bin_PROGRAMS = 42sh
42sh_SOURCES = 42sh.c
42sh_CPPFLAGS = -I%D%
42sh_CFLAGS = -std=c99 -Werror -Wall -Wextra -Wvla -pedantic
42sh_LDADD = \
ast/libast.a \
parser/libparser.a
To check that your build system is valid, run the make distcheck
command.
make distcheck
is a command used to create and test a distribution
package. It consists of configuring, building and checking the
software in a separate directory to ensure that the distribution
package is complete and functional. This allows potential problems
to be detected before the software is distributed to other systems.
How to make the target
Now that everything is ready, we are going to launch the build. If you have not already done so, you need to configure the build.
42sh$ autoreconf --force --verbose --install
42sh$ ./configure
Let's launch the build.
42sh$ make
You can use make -j4
to parallelize the build on 4 cpu cores.
Hooking a testsuite
The build system should allow you to run tests from it. To do this,
you will need to add a Makefile.am to the tests folder. Then add the
check-local
rule.
check-local:
./testsuite.sh
Don't forget to add makefile to configure.ac and to add the tests folder to Makefile.am at the root.